package com.fly.core;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.hibernate.SQLQuery;
import org.hibernate.transform.Transformers;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

/**
 * 
 * 原生SQL执行类
 * 
 * @author 00fly
 * @version [版本号, 2018-09-11]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@SuppressWarnings({"unchecked", "rawtypes"})
public class NativeQuery implements DataRepository
{
    private EntityManager em;
    
    private String sql;
    
    private String countSql;
    
    private Object[] params = {};
    
    private Map<String, Object> paramMap = new HashMap<>();
    
    public NativeQuery(EntityManager em)
    {
        super();
        this.em = em;
    }
    
    public NativeQuery setSql(String sql)
    {
        this.sql = sql;
        return this;
    }
    
    /**
     * 设置记录总条数sql
     * 
     * @param countSql
     * @return
     * @see [类、类#方法、类#成员]
     */
    public NativeQuery setCountSql(String countSql)
    {
        this.countSql = countSql;
        return this;
    }
    
    public NativeQuery setParams(Object... params)
    {
        this.params = params;
        return this;
    }
    
    public NativeQuery setParams(Map<String, Object> params)
    {
        this.paramMap = params;
        return this;
    }
    
    @Override
    public Map<String, Object> map()
    {
        Query query = em.createNativeQuery(sql).setFirstResult(0).setMaxResults(1);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        List<Map<String, Object>> list = query.getResultList();
        if (list.isEmpty())
        {
            return Collections.emptyMap();
        }
        return list.get(0);
    }
    
    @Override
    public List<Map<String, Object>> maps()
    {
        Query query = em.createNativeQuery(sql);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }
    
    @Override
    public List<Map<String, Object>> maps(int length)
    {
        Query query = em.createNativeQuery(sql).setFirstResult(0).setMaxResults(length);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }
    
    @Override
    public List<Map<String, Object>> maps(int pageNo, int pageSize)
    {
        Query query = em.createNativeQuery(sql).setFirstResult((pageNo - 1) * pageSize).setMaxResults(pageSize);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        return query.getResultList();
    }
    
    @Override
    public Page mapPage(int pageNo, int pageSize)
    {
        Pageable pageable = new PageRequest(pageNo - 1, pageSize);
        return new PageImpl<>(maps(pageNo, pageSize), pageable, count());
    }
    
    @Override
    public <T> T model(Class<T> resultClass)
    {
        Query query = em.createNativeQuery(sql).setFirstResult(0).setMaxResults(1);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(resultClass));
        Object object = query.getSingleResult();
        if (object != null)
        {
            return (T)object;
        }
        return null;
    }
    
    @Override
    public <T> List<T> models(Class<T> resultClass)
    {
        Query query = em.createNativeQuery(sql);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(resultClass));
        return query.getResultList();
    }
    
    @Override
    public <T> List<T> models(Class<T> resultClass, int length)
    {
        Query query = em.createNativeQuery(sql).setFirstResult(0).setMaxResults(length);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(resultClass));
        return query.getResultList();
    }
    
    @Override
    public <T> List<T> models(Class<T> resultClass, int pageNo, int pageSize)
    {
        Query query = em.createNativeQuery(sql).setFirstResult((pageNo - 1) * pageSize).setMaxResults(pageSize);
        setParams(query);
        query.unwrap(SQLQuery.class).setResultTransformer(Transformers.aliasToBean(resultClass));
        return query.getResultList();
    }
    
    @Override
    public <T> Page<T> modelPage(Class<T> resultClass, int pageNo, int pageSize)
    {
        Pageable pageable = new PageRequest(pageNo - 1, pageSize);
        return new PageImpl(models(resultClass, pageNo, pageSize), pageable, count());
    }
    
    /**
     * 设置参数
     * 
     * @param query
     * @see [类、类#方法、类#成员]
     */
    private void setParams(Query query)
    {
        if (sql.contains("?"))
        {
            int i = 1;
            for (Object obj : params)
            {
                query.setParameter(i, obj);
                i++;
            }
        }
        else if (!paramMap.isEmpty())
        {
            for (String key : paramMap.keySet())
            {
                query.setParameter(key, paramMap.get(key));
            }
        }
    }
    
    @Override
    public long count()
    {
        if (countSql != null)
        {
            Query countQuery = em.createNativeQuery(countSql);
            setParams(countQuery);
            Object obj = countQuery.getSingleResult();
            return Long.parseLong(obj.toString());
        }
        return maps().size();
    }
    
    @Override
    public boolean exists()
    {
        if (countSql != null)
        {
            Query countQuery = em.createNativeQuery(countSql);
            setParams(countQuery);
            Object obj = countQuery.getSingleResult();
            return Long.parseLong(obj.toString()) > 0;
        }
        return map() != null;
    }
    
    @Override
    public long updateOrDelete()
    {
        Query query = em.createNativeQuery(sql);
        setParams(query);
        return query.executeUpdate();
    }
}
